/**
 * @author renato
 * Created on 22/09/2005
 */
package br.unb.core.traffic;

import java.io.File;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

import br.unb.core.dispatcher.Dispatcher;
import br.unb.core.manager.Manager;
import br.unb.data.DataGenerator;
import br.unb.data.FileData;
import br.unb.data.RandomData;
import br.unb.plugins.geral.model.GeneratorModel;
import br.unb.statistic.InterPacketTimeGenerator;
import br.unb.statistic.RandomGenerator;
import br.unb.utils.Console;
import br.unb.utils.ConsoleInterface;
import br.unb.utils.LogFile;
import br.unb.utils.LogPrinter;
import br.unb.utils.Path;
import br.unb.utils.Resource;

import cgl.narada.service.timer.HRClock;
import javax.swing.text.DefaultStyledDocument;

/********************* Main code for the sender machine ***********************/
public class SenderController implements Runnable, ConsoleInterface {

    HRClock clock = (HRClock) HRClock.getClock();
    private Traffic traffic;
    private boolean isRunning;
    private Thread senderThread;
    private Thread senderTimer;
    /*Data generator*/
    private DataGenerator dataGenerator;
    private InterPacketTimeGenerator pauseGenerator;
    private RandomGenerator lengthGenerator;
    private TrafficDatagramPacket tdPacket;
    private DatagramSocket trafficSocket;
    private int packetNum;
    //Files
    private LogPrinter timestampPrinter;

    /**
     * @param lengthGenerator
     * @param pauseGenerator
     */
    public SenderController(Traffic td) throws SocketException {
        traffic = td;
        /*Resource.printError(
        "Sender machine: init()! "
        + traffic.getName());//debug
         */
        try {
            timestampPrinter = new LogPrinter(new LogFile(
                    LogFile.Type.SENDER,
                    traffic.getName()));
        } catch (Exception e) {
            printError("ERROR_OPENING_LOG_FILE_ON_SENDER");
        }
        /******************* Data Generator *******************/
        File file = null;
        if (traffic.getPath() != null && !traffic.getPath().equals("")) {
            file = new File(traffic.getPath());
            if (!file.canRead()) {
                file = new File(Path.getHomeDir(), traffic.getPath());
            }
        }
        if (file != null && file.canRead()) {
            dataGenerator = new FileData(file);
        } else {
            //TODO println("file not found!");
            dataGenerator = new RandomData();
        }
        /******************* Length Generator *******************/
        lengthGenerator = (RandomGenerator) (traffic.getLengthModel()).getGenerator();

        /****************** Time/Pause generator **********************/
        double duration = 0;
        if (traffic.getStart() >= 0 && traffic.getDuration() > 0 && traffic.getDuration() > traffic.getStart()) {
            duration = traffic.getDuration() - traffic.getStart();
        } else {
            //TODO println("invalid interval, using default of 60 seconds");
            traffic.setStart(0);
            traffic.setDuration(60);
            duration = 60;
        }

        GeneratorModel genModel = traffic.getPauseModel();
        genModel.setParameter("duration", "" + duration);
        genModel.setParameter("name", traffic.getName());
        pauseGenerator = (InterPacketTimeGenerator) genModel.getGenerator();

        trafficSocket = new DatagramSocket(traffic.getSrcPort());
        trafficSocket.setTrafficClass(this.traffic.tosToIntDHCP(traffic.getTos()));
    }

    public boolean isRunning() {
        if (isRunning) {
            return true;
        } else {
            return false;
        }
    }

    public Traffic getTraffic() {
        return traffic;
    }

    /******************
     * Methods below will be called from AWT-EventQueuer
     * start()
     * stop()
     * They are used to manage the running thread using shared variable isRunning
     ******************/
    public void start() {
        if (this.isRunning) {
            return;
        }
        /*Console.printError(
        "Sender machine: start()! "
        + Resource.getString("BEGIN_SENDING")
        + traffic.getName()
        + Resource.getString("PORT_USED")
        + traffic.getDstPort());//Debug
         */
        this.isRunning = true;
        senderThread = new Thread(this, "SenderController-Main");
        senderThread.setPriority(Thread.MAX_PRIORITY - 1);
        senderThread.start();//calls this.run()
    }

    public void stop() {
        /*
        Console.printError(
        "Sender machine: stop()! "
        + Resource.getString("STOP_SENDING")
        + traffic.getName()
        + ". "
        + Resource.getString("PORT_RELEASED")
        + traffic.getDstPort());//debug
         */
        if (isRunning) {
            senderThread.setPriority(Thread.NORM_PRIORITY);
            isRunning = false;//Shared !! will stop other thread
            packetNum = -2;

            try {
                this.send();//why this thing is here??, sends a last packet to the receiver 
            } catch (IOException e) {
                //TODO println("ioexception, sending error");
            }
            senderThread.interrupt();
            try {
                this.timestampPrinter.close();
            } catch (Exception e) {
                printError("ERROR_CLOSING_LOG_FILE_ON_SENDER");
            }
            trafficSocket.close();
        }
        Dispatcher.getInstance().sendSenderFinishedAck(traffic);
        println("stop()!");
        if (Thread.currentThread().isAlive()) {
            Thread.yield();
        }
    }

    /*********************************************************
     * Methods below will run in a new Thread (spawnned from AWT-eventQueue)
     * run()
     *  -send();
     *   -newPacket();
     *   -printToFiles();
     *********************************************************/
    public void run() {
        /*
        Resource.printError(
        "Sender machine: run()! "
        + traffic.getName());//debug
         */
        /**Minimum time to sleep in the Thread.Sleep method*/
        final int MIN_MIL = 10;
        long pauseTime = 0;
        int countIoException = 0;
        /*Sleeps until the start time*/
        try {
            Thread.sleep(this.traffic.getStart() * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /*
        println(
        Resource.getString("INIT_TIME")
        + traffic.getName());//debug
         */
        if (traffic.getStart() >= 0) {
            /*Timer that clocks the duration of the traffic*/
            final SenderController THIS = this;
            senderTimer = new Thread(new Runnable() {

                public void run() {
                    try {
                        Thread.sleep((traffic.getDuration() - traffic.getStart()) * 1000);
                    } catch (InterruptedException ie) {
                        //ie.printStackTrace();
                        //senderTimer.printError("Sender machine: run(), InterruptedException");
                        Thread.yield(); //REVIEW!!!!!!!
                    }

                    //TrafficManager.senderList.stopSender(traffic.getName());//Used to print verbosity messages.
                    //Could be "this.stop()"
                    println("senderTimer.run(): Normal exit");
                    THIS.stop();
                }
            }, "SenderController-Timer");//Thread name
            senderTimer.setPriority(Thread.MAX_PRIORITY);
            senderTimer.start();
        }
        long tempo;
        while (isRunning) {
            /*
             * "isRunning" is Shared between the running threads!
             * Stays in this loop sending packets until the timer setted above expires
             * or someone else stops the senderController
             */
            try {
                tempo = clock.getTimeMicroseconds();
                //	System.out.println("Sending package!");
                this.send();
                pauseTime = this.nextPause();
                if (pauseTime > (MIN_MIL * 1000)) {
                    try {
                        Thread.sleep((pauseTime - (MIN_MIL * 1000)) / 1000);
                    } catch (InterruptedException ie) {
                        Thread.yield();
                    }
                }
                this.sleepInMicro(pauseTime - (clock.getTimeMicroseconds() - tempo));

                countIoException = 0;
            } catch (IOException e) {
                countIoException++;
                if (countIoException > 5) {
                    Console.showMessageDialog(
                            Resource.getString("IO_EXCEPTION_GENERATED") + e.getMessage());
                    this.stop();
                }
                this.packetNum--;
            }
            /*
            //This stop condition is used to prevent eternal loop
            if((int)this.timeCount > (this.traffic.getDuration()-this.traffic.getStart())*1000000){
            this.stop();
            }*/
        }
        println("SenderController.run(): Normal exit");
    }

    /*********************** Sender specifc methods ***************************/
    /**
     * Send a packet
     */
    private void send() throws IOException {
        trafficSocket.send(this.newPacket());
        printToFiles();
    }

    /**
     * Creates a new packet structure
     */
    private DatagramPacket newPacket() {
        try {
            int nextLen;
            byte[] packetData;
            if (isRunning) {
                nextLen = this.nextLength();
                packetData = new byte[nextLen];
                nextLen = dataGenerator.getNextData(packetData, nextLen);
            } else {
                nextLen = this.nextLength();
                packetData = new byte[nextLen];
                packetData[0] = -1;
            }
            if (nextLen < 0) {
                this.stop();
                return null;
            }
            tdPacket = new TrafficDatagramPacket(
                    traffic.getDstIP(),
                    traffic.getDstPort(),
                    (16 + nextLen),
                    ++packetNum,
                    packetData);
            return this.tdPacket.getTrafficPacket();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Returns a random value for packet size
     */
    private int nextLength() {
        return (int) (this.lengthGenerator.rand());
    }

    /**
     * Returns a pause between packets tramission in microseconds 
     * @return
     */
    private long nextPause() {
        return (long) (1000000 * this.pauseGenerator.nextInterPacketTime());
    }

    public void sleepInMicro(long n) {
        long tempo = 0, tempo2 = 0;
        int cp = Thread.currentThread().getPriority();
        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);
        if (n <= 0) {
            return;
        }
        tempo2 = clock.getTimeMicroseconds();
        while (tempo < n) {
            tempo = clock.getTimeMicroseconds() - tempo2;
        }
        Thread.currentThread().setPriority(cp);
    }

    private void printToFiles() {
        try {
            timestampPrinter.logEntry(
                    tdPacket.getPacketNum(), (tdPacket.getPacketLen() - 16), tdPacket.getTimestamp().getTimeInMicroSeconds(), 0);
            // */
        } catch (Exception e) {
            printError("LOGGING_ERROR_ON_SENDER");
        }
    }
    /**Console interface methods*/
    public static DefaultStyledDocument doc = null;

    @Override
    public void printError(String s) {
        Console.println(s, System.err, doc);
    }

    @Override
    public void println(String s) {
        Console.println(s, System.out, doc);
    }

    @Override
    public void setDocument(DefaultStyledDocument doc) {
        SenderController.doc = doc;
    }
}
